package com.tangsm.spring.boot.validation.util;

import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;

/**
 * HttpsClient 带证书工具类
 * <p>参考：https://blog.csdn.net/w993263495/article/details/85693911</p>
 * @author tangsm
 * @date 2022/01/07 15:00
 */
public class HttpsClientUtils {
    private static final Logger log = LoggerFactory.getLogger(HttpsClientUtils.class);


    /**
     * 连接超时毫秒 ps：表示建立连接的超时时间
     */
    private final static int CONNECT_TIMEOUT = 3000;

    /**
     * 传输超时毫秒 ps：表示数据传输处理时间
     */
    private final static int SOCKET_TIMEOUT = 10000;

    /**
     * 从线程池获取连接超时时间毫秒
     */
    private final static int REQUESTCONNECT_TIMEOUT = 2000;

    /**
     * 线程池的最大连接数
     */
    private final static int MAX_TOTAL = 100;

    /**
     * 每个路由默认基础的连接数
     */
    private final static int CONNECT_DEFAULT_ROUTE = 10;

    /**
     * 发送 HTTPS POST请求，参数格式JSON
     * <p>使用指定的证书文件及密码，不带请求头信息</p>
     *
     * @param url 请求地址
     * @param param 请求参数（Json格式）
     * @param path 证书文件路径
     * @param password 证书密码
     * @return 请求结果
     */
    public static String httpsPost(String url, String param, String path, String password){
        String result = null;

        try {
            HttpPost httpPost = new HttpPost(url);
            // 设置请求头
            httpPost.addHeader("Content-Type", "application/json; charset=UTF-8");
            // 设置请求参数
            httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));

            // 发送 HTTPS 请求
            result = doHttps(httpPost, path, password);
        } catch (Exception e) {
            log.error("发送 HTTPS POST请求，使用指定的证书文件及密码，不带请求头信息异常，Url：{}", url, e);
        }

        return result;
    }

    /**
     * 发送 HTTPS POST请求，参数格式XML
     *
     * <p>使用指定的证书文件及密码，请求头为“application/xml;charset=UTF-8”</p>
     *
     * @param url 请求地址
     * @param param 请求参数
     * @param path 证书文件路径
     * @param password 证书密码
     * @return 请求结果
     */
    public static String httpsPostXml(String url, String param, String path, String password){
        String result = null;

        try {
            HttpPost httpPost = new HttpPost(url);
            // 设置请求头
            httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8");
            // 设置请求参数
            httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));
            // 发送 HTTPS 请求
            result = doHttps(httpPost, path, password);
        } catch (Exception e) {
            log.error("发送 HTTPS POST请求，使用指定的证书文件及密码，请求头为“application/xml;charset=UTF-8”异常，Url：{}", url, e);
        }

        return result;
    }

    /**
     * 发送 HTTPS 请求
     * <p>使用指定的证书文件及密码</p>
     *
     * @param request 请求属性
     * @param path 证书文件路径
     * @param password 证书密码
     * @return 请求结果
     * @throws Exception 异常信息
     */
    private static String doHttps(HttpRequestBase request, String path, String password) throws Exception {
        // 获取HTTPS SSL证书
        SSLConnectionSocketFactory csf = getSSLFactory(path, password);

        RequestConfig.Builder builder = RequestConfig.custom();
        // 请求配置项：设置传输超时、连接超时、从线程池获取连接超时
        RequestConfig config = builder.setSocketTimeout(SOCKET_TIMEOUT)
                .setConnectTimeout(CONNECT_TIMEOUT)
                .setConnectionRequestTimeout(REQUESTCONNECT_TIMEOUT).build();

        // 初始化HttpClient
        CloseableHttpClient httpClient = HttpClients.custom()
                .setMaxConnPerRoute(CONNECT_DEFAULT_ROUTE)
                .disableConnectionState().setDefaultRequestConfig(config)
                .setSSLSocketFactory(csf).build();

        return doRequest(httpClient, request);
    }

    /**
     * 获取HTTPS SSL连接工厂
     * <p>使用指定的证书文件及密码</p>
     *
     * @param path     证书全路径
     * @param password 证书密码
     * @return HTTPS连接工厂
     * @throws Exception 异常信息
     */
    private static SSLConnectionSocketFactory getSSLFactory(String path, String password) throws Exception {

        // 初始化证书，指定证书类型为“PKCS12”
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        // 读取指定路径的证书

        try (FileInputStream input = new FileInputStream(path)) {
            // 装载读取到的证书，并指定证书密码
            keyStore.load(input, password.toCharArray());
        }

        // 获取HTTPS SSL证书连接上下文
        SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, password.toCharArray()).build();

        // 获取HTTPS连接工厂，指定TSL版本
        return new SSLConnectionSocketFactory(sslContext,
                new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"},
                null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    }


    /**
     * 处理Http/Https请求，并返回请求结果
     * <p>注：默认请求编码方式 UTF-8</p>
     *
     * @param httpClient 通过连接池获取连接对象
     * @param request 请求属性
     * @return 请求结果
     * @throws IOException 异常信息
     */
    private static String doRequest(CloseableHttpClient httpClient, HttpRequestBase request) throws IOException {
        String result;

        try (CloseableHttpResponse response = httpClient.execute(request)) {
            // 获取请求结果
            // 解析请求结果
            HttpEntity entity = response.getEntity();
            // 转换结果
            result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name());
            // 关闭IO流
            EntityUtils.consume(entity);
        }

        return result;
    }

    public static void main(String[] args) {
        httpsPost("https://www.baidu.com/con?from=self?_t=1574995966634", "", "", "");
    }
}
